package controllers

import akka.actor.{Actor, ActorSystem, PoisonPill, Props}
import akka.stream.Materializer
import command.CommandUtils
import dao._
import implicits.Implicits._
import models.Tables._
import org.zeroturnaround.zip.ZipUtil
import play.api.i18n.I18nSupport
import play.api.libs.streams.ActorFlow
import play.api.mvc._
import shared.pojo.WorkflowMission.MissionNameCheck
import shared.pojo.mission._
import tool._
import tool.soft._
import upickle.default._
import utils.Utils

import java.io.File
import javax.inject.Inject
import scala.collection.immutable.SeqMap
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.language.postfixOps

/** Created by yz on 14/7/2020
  */
class MissionController @Inject() (cc: ControllerComponents)(
    implicit val missionDao: MissionDao,
    implicit val workflowMissionDao: WorkflowMissionDao,
    implicit val matrixFileDataDao: MatrixFileDataDao,
    implicit val system: ActorSystem,
    implicit val materializer: Materializer
) extends AbstractController(cc)
    with I18nSupport {

  def missionNameCheck = Action.async { implicit request =>
    val data = FormTool.missionNameForm.bindFromRequest.get
    val userId = Tool.getUserId
    missionDao.selectOp(userId, data.missionName).map { mission =>
      mission match {
        case Some(y) => Ok(ujson.Obj("valid" -> false))
        case None =>
          Ok(ujson.Obj("valid" -> true))
      }
    }
  }

  def nameCheck = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[MissionNameCheck.Request])
    missionDao.selectOp(data.id, data.missionName).map { mission =>
      mission match {
        case Some(y) => Ok(ujson.Obj("valid" -> false))
        case None =>
          Ok(ujson.Obj("valid" -> true))
      }
    }
  }

  def getConfigData = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.select(data.missionId).map { row =>
      val workspaceDir = Tool.getMissionWorkspaceDir(row)
      val json = Utils.configFile2Json(workspaceDir)
      val finalJson = json
      Ok(json)
    }
  }

  def updateName = Action.async { implicit request =>
    val missionName = FormTool.missionNameForm.bindFromRequest().get.missionName
    val missionId = FormTool.missionIdForm.bindFromRequest().get.missionId
    val userId = Tool.getUserId
    missionDao.select(missionId).flatMap { row =>
      val newRow = row.copy(missionName = missionName)
      missionDao.update(newRow).map { x =>
        Ok(writeJs("success"))
      }
    }
  }

  def getLogContent = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.select(data.missionId).map { mission =>
      val missionIdDir = Tool.getMissionIdDir(mission)
      val logFile = new File(missionIdDir, s"log.txt")
      val logStr = logFile.str
      Ok(logStr)
    }
  }

  def downloadResult = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val missionId = data.missionId
    missionDao.select(missionId).map { mission =>
      val missionIdDir = Tool.getMissionIdDir(mission)
      val resultFile = new File(missionIdDir, s"result.zip")
      val resultDir = new File(missionIdDir, "result")
      ZipUtil.pack(resultDir, resultFile)
      Ok.sendFile(resultFile)
        .withHeaders(
          CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${mission.missionName}_result.zip"),
          CONTENT_TYPE -> "application/x-download"
        )
    }
  }

  def downloadResultFile = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val missionId = data.missionId
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    missionDao.select(missionId).map { mission =>
      val missionIdDir = Tool.getMissionIdDir(mission)
      val resultDir = new File(missionIdDir, "result")
      val file = new File(resultDir, fileName)
      Ok.sendFile(file)
        .withHeaders(
          CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${file.getName}"),
          CONTENT_TYPE -> "application/x-download"
        )
    }
  }

  def deleteMissionById = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val userId = Tool.getUserId
    missionDao.select(data.missionId).flatMap { mission =>
      Future {
        val workspaceDir = Tool.getMissionWorkspaceDir(mission)
        CommandUtils.killPid(workspaceDir)
        val missionIdDir = Tool.getMissionIdDir(mission)
        Utils.deleteDirectory(missionIdDir)
      }
      missionDao.delete(userId, data.missionId).map { x =>
        Ok(writeJs("success"))
      }
    }
  }

  def deletes = Action.async { implicit request =>
    val data = FormTool.idsForm.bindFromRequest().get
    val userId = Tool.getUserId
    missionDao.selectAll(data.ids).flatMap { missions =>
      Future {
        missions.foreach { mission =>
          val workspaceDir = Tool.getMissionWorkspaceDir(mission)
          CommandUtils.killPid(workspaceDir)
          val missionIdDir = Tool.getMissionIdDir(mission)
          Utils.deleteDirectory(missionIdDir)
        }
      }
      missionDao.deletes(data.ids).map { x =>
        Ok(writeJs("success"))
      }
    }
  }

  def getDiffFeatures = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val missionId = data.missionId
    val fileNameOp = FormTool.fileNameOpForm.bindFromRequest().get.fileName
    val workspaceF = Tool.getWorkspaceDirF(None, missionId)
    workspaceF.map { workspaceDir =>
      val fileName = fileNameOp.get
      val file = new File(workspaceDir, fileName)
      val array = file.txtLines.map(_.head)
      Ok(writeJs(array))
    }

  }

  def missionManageBefore = Action { implicit request =>
    Ok(views.html.user.mission.manage())
  }

  def resultDetailBefore = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.select(data.missionId).map { row =>
      val curSoft = Tool.kindSoftMap(row.kind)
      val resultHtml = curSoft.getResultHtml(data.missionId)
      Ok(resultHtml)
    }
  }

  def getTrueFileName = Action { implicit request =>
    val data = FormTool.fileNameForm.bindFromRequest().get
    val trueFileName = Tool.getTrueFileName(data.fileName)
    Ok(ujson.Obj("fileName" -> trueFileName))
  }

  def getMissionById = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.select(data.missionId).map { x =>
      val trueInputs = x.input.map { fileName =>
        Tool.getTrueFileName(fileName)
      }
      val newX = x.copy(input = trueInputs)
      val json = writeJs(newX)
      Ok(json)
    }
  }

  def getFileData = Action.async { implicit request =>
    val messages = request.messages
    val data = FormTool.missionIdForm.bindFromRequest().get
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    missionDao.select(data.missionId).map { mission =>
      val resultDir = Tool.getMissionResultDir(mission)
      val file = new File(resultDir, fileName)
      val (columnNames, array) = file.nameSuffix match {
        case "xlsx" => {
          val lines = file.xlsxLinesWithColors()
          val columnNames = lines.head.map(_._1)
          val columnIndexs = columnNames.indices.map(_.toString)
          val array = lines.drop(1).map { columns =>
            columnIndexs
              .zip(columns)
              .map { case (index, (v, color)) =>
                (index -> ujson.Obj("color" -> s"${color}", "value" -> v))
              }
              .toMap
          }
          (columnNames, writeJs(array))
        }
        case _ => {
          val lines = if (file.nameSuffix == "xls") {
            file.txtLines
          } else file.csvLines
          val noHeaderFileNames = List("lefse_LDA.xls", "lefse_LDA_diff.xls")
          val hasHeader = !noHeaderFileNames.contains(fileName)
          val columnNames = if (hasHeader) {
            lines.head
          } else List.empty
          val columnIndexs = lines.head.indices.map(_.toString)
          val contents = if (hasHeader) {
            lines.drop(1)
          } else lines
          val array = contents.map { columns =>
            columnIndexs.zip(columns).toMap
          }
          (columnNames, writeJs(array))
        }
      }
      val json = ujson.Obj("columnNames" -> columnNames, "array" -> array)
      Ok(json)
    }
  }

  def getResultFileInfo = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.select(data.missionId).map { mission =>
      val curSoft = Tool.kindSoftMap(mission.kind)
      val workspaceDir = Tool.getMissionWorkspaceDir(mission)
      val fileInfos = curSoft.getFileInfos(workspaceDir, 0)
      val array = fileInfos.map { fileInfo =>
        val resultDir = Tool.getMissionResultDir(mission)
        val file = new File(resultDir, s"${fileInfo.fileRelPath}/${fileInfo.fileName}")
        val rowNum = file.nameSuffix match {
          case "csv" => file.csvLines.drop(1).size.toString
          case "xls" => file.txtLines.drop(1).size.toString
          case _ => "-"
        }
        ujson.Obj(
          "fileName" -> fileInfo.fileName,
          "rowNum" -> rowNum,
          "intro" -> fileInfo.intro,
          "tags" -> fileInfo.tags,
          "dataFileName" -> fileInfo.dataFileName,
          "fileRelPath" -> fileInfo.fileRelPath
        )
      }
      Ok(writeJs(array))
    }
  }

  def getAllMissionResult = Action.async { implicit request =>
    val userId = Tool.getUserId
    val kindOp = FormTool.kindOpForm.bindFromRequest().get.kind
    val fileNameOp = FormTool.fileNameOpForm.bindFromRequest().get.fileName
    val dbMetaboliteNamesF = fileNameOp
      .map { fileName =>
        Tool.produceRawDataLinesF(userId, fileName).map { lines =>
          lines.drop(1).map(_.head)
        }
      }
      .getOrElse(Future {
        List.empty
      })

    val f = kindOp
      .map { kind =>
        val kinds = kind.split(",")
        missionDao.selectAll(userId, kinds)
      }
      .getOrElse {
        missionDao.selectAll(userId, PreDealSoft.nameStr)
      }
    dbMetaboliteNamesF.zip(f).map { case (dbMetaboliteNames, missions) =>
      val array = missions
        .map { mission =>
          val curSoft = Tool.kindSoftMap(mission.kind)
          val resultFileName = curSoft.getResultFileName
          val finalFileName = if (dbMetaboliteNames.isEmpty) {
            resultFileName
          } else {
            val workspaceDir = Tool.getMissionWorkspaceDir(mission)
            val file = new File(workspaceDir, resultFileName)
            val metaboliteNames = file.csvLines.selectOneColumn(0).drop(1)
            if (dbMetaboliteNames.containsAll(metaboliteNames)) {
              resultFileName
            } else ""
          }
          val map = SeqMap("fileName" -> finalFileName)
          writeJs(mission).toObj ++ writeJs(map).toObj
        }
        .filter { map =>
          map("fileName").str.nonEmpty
        }
      Ok(writeJs(array))
    }
  }

  def viewCsvFileContent = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.select(data.missionId).map { row =>
      val resultDir = Tool.getMissionResultDir(row)
      val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
      val file = new File(resultDir, fileName)
      val str = file.str
      Ok(views.html.user.tool.fileView(str))
    }
  }

  def getAllMission = Action.async { implicit request =>
    val userId = Tool.getUserId
    missionDao.selectAll(userId).map { x =>
      Future {
        val missionIds = x.map(_.id.toString)
        val kinds = Tool.softs.map(_.nameStr)
        kinds.foreach { kind =>
          val missionKindDir = Tool.getUserMissionKindDir(kind)
          missionKindDir
            .listFiles()
            .filter { dir =>
              !missionIds.contains(dir.getName)
            }
            .foreach(Utils.deleteDirectory(_))
        }
      }
      val array = Utils.getArrayByTsDefault(x)
      Ok(writeJs(array))
    }
  }

  def getAllKind = Action.async { implicit request =>
    val userId = Tool.getUserId
    missionDao.selectAllKind(userId).map { x =>
      val array = x
      Ok(writeJs(array))
    }
  }

  def getSingleColumnAll = Action.async { implicit request =>
    val data = FormTool.columnNameForm.bindFromRequest().get
    missionDao.selectSingleColumn(data.columnName).map { xs =>
      val json = writeJs(xs)
      Ok(json)
    }
  }

  def updateMissionSocket = WebSocket.accept[ujson.Value, ujson.Value] { implicit request =>
    val userId = Tool.getUserId
    case class MissionAction(beforeMissions: Seq[MissionRow], action: String)
    ActorFlow.actorRef(out =>
      Props(new Actor {
        override def receive: Receive = {
          case msg: ujson.Value if msg("info").str == "start" =>
            val beforeMissions = Utils.execFuture(missionDao.selectAll(userId))
            out ! Utils.getJsonByTs(beforeMissions)
            system.scheduler.scheduleOnce(3 seconds, self, MissionAction(beforeMissions, "update"))
          case MissionAction(beforeMissions, action) =>
            missionDao.selectAll(userId).map { missions =>
              val currentMissions = missions
              if (currentMissions.size != beforeMissions.size) {
                out ! Utils.getJsonByTs(currentMissions)
              } else {
                val b = currentMissions.zip(beforeMissions).forall { case (currentMission, beforeMission) =>
                  currentMission.id == beforeMission.id && currentMission.state == beforeMission.state
                }
                if (!b) {
                  out ! Utils.getJsonByTs(currentMissions)
                }
              }
              system.scheduler.scheduleOnce(3 seconds, self, MissionAction(currentMissions, "update"))
            }
          case _ =>
            self ! PoisonPill
        }

        override def postStop(): Unit = {
          self ! PoisonPill
        }
      })
    )
  }

  def reRunMissionBefore = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.select(data.missionId).map { row =>
      val beforeMissionName = s"${row.missionName}"
      val r = "^.*_(\\d{1,4})$".r
      val missionName = if (beforeMissionName.matches(r.regex)) {
        "_(\\d{1,4})$".r.replaceAllIn(
          beforeMissionName,
          replacer => {
            s"_${(replacer.group(1).toInt + 1)}"
          }
        )
      } else s"${beforeMissionName}_1"
      val curSoft = Tool.kindSoftMap(row.kind)
      val html = curSoft.getHtml(missionName, Some(data.missionId))
      Ok(html)
    }
  }

  def getFileNamesExistInfo = Action.async { implicit request =>
    val data = FormTool.fileNamesForm.bindFromRequest.get
    val userId = Tool.getUserId
    Tool.getFilesExistF(userId, data.fileNames).map { ts =>
      val map = ts.toMap
      Ok(writeJs(map))
    }
  }

  def getCcaRdaXInfo = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.select(data.missionId).map { row =>
      val workspaceDir = Tool.getMissionWorkspaceDir(row)
      val file = new File(workspaceDir, "samples.csv")
      val xs = file.csvLines.head.drop(1)
      Ok(writeJs(xs))
    }
  }

  def getRowColSize = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    missionDao.select(data.missionId).map { row =>
      val workspaceDir = Tool.getMissionWorkspaceDir(row)
      val file = new File(workspaceDir, fileName)
      val lines = file.txtLines
      val xs = lines.size
      Ok(ujson.Obj("rowNum" -> (xs - 1), "colNum" -> (lines.head.size - 1)))
    }
  }

  def getEdgeInfo = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    missionDao.select(data.missionId).map { row =>
      val workspaceDir = Tool.getMissionWorkspaceDir(row)
      val file = new File(workspaceDir, fileName)
      val edgeInfos = file.csvLines.lineSeqMap
      Ok(writeJs(edgeInfos))
    }
  }

  def getCsvFileInfo = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    missionDao.select(data.missionId).map { row =>
      val workspaceDir = Tool.getMissionWorkspaceDir(row)
      val file = new File(workspaceDir, fileName)
      val edgeInfos = file.csvLines.lineSeqMap
      Ok(writeJs(edgeInfos))
    }
  }

  def canPaired = Action {
    implicit request =>
      val data = Tool.transformRq.transform(reader[CanPaired.Request])
      val userId = Tool.getUserId
      val groupFile = Tool.getGroupFile(userId, data.groupFile)
      val dataLines = Utils.execFuture(Tool.produceRawDataLinesF(userId, data.file))
      val sampleNames = dataLines.head.drop(1)
      val lines = groupFile.txtLines
      val maps = lines.lineSeqMapLower.filter { map =>
        sampleNames.contains(map("sample"))
      }
      val hasPairID = groupFile.txtLines.head.containsIgnoreCase("Pair_ID")
      val b = if (hasPairID) {
        val groups = maps.groupBy {
          map =>
            map("classnote")
        }
        val hasSampleSize = groups.forall {
          case (classnote, inMaps) =>
            (inMaps.size / maps.size.toDouble) == 0.5
        }
        val isUniq = groups.forall {
          case (classnote, inMaps) =>
            inMaps.map(_("pair_id")).isUniq
        }
        val sets = groups.map {
          case (classnote, inMaps) =>
            inMaps.map(_("pair_id")).toSet
        }
        val isSame = sets.forall {
          set =>
            set == sets.head
        }
        hasSampleSize && isUniq && isSame
      } else false
      Ok(writeJs(b))
  }

}
